package org.springframework.extend.persistence.repository.config;

import java.util.ArrayList;
import java.util.List;

import org.springframework.aop.aspectj.AspectJAroundAdvice;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.aspectj.AspectJPointcutAdvisor;
import org.springframework.aop.config.AspectComponentDefinition;
import org.springframework.aop.config.MethodLocatingFactoryBean;
import org.springframework.aop.config.PointcutComponentDefinition;
import org.springframework.aop.config.SimpleBeanFactoryAwareAspectInstanceFactory;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.parsing.CompositeComponentDefinition;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.extend.persistence.PersistenceContainer;
import org.w3c.dom.Element;

public class RepositoryAspectParser extends AbstractExtendBeanDefinitionParser implements BeanDefinitionParser {

	private static final String	PROPERTY_REPOSITORY					= "repository";
	private static final String	TARGET_METHOD_NAME					= "proceedRequest";
	private static final String	ASPECT_ID							= "persisteceRepositoryAspect";

	private static final String	BEAN_NAME_TARGET_ASPECT				= "persisteceRepositoryAspectTarget";
	private static final String	BEAN_NAME_OF_REPOSITORY_POINTCUT	= "repositoryService";

	private static final String	SIMPLE_TEMPLATE_EXPRESSION			= "execution(* org.springframework.jdbc.core.simple.SimpleJdbcTemplate.query*(..))";
	private static final String	SIMPLE_TEMPLATE_UPDATE_EXPRESSION	= "execution(* org.springframework.jdbc.core.simple.SimpleJdbcTemplate.update*(..))";
	private static final String	SIMPLE_TEMPLATE_BATCH_EXPRESSION	= "execution(* org.springframework.jdbc.core.simple.SimpleJdbcTemplate.batch*(..))";

	private static final String	COMMON_TEMPLATE_EXPRESSION			= "execution(* org.springframework.jdbc.core.JdbcTemplate.query*(..))";
	private static final String	COMMON_TEMPLATE_EXECUTE_EXPRESSION	= "execution(* org.springframework.jdbc.core.JdbcTemplate.execute*(..))";
	private static final String	COMMON_TEMPLATE_UPDATE_EXPRESSION	= "execution(* org.springframework.jdbc.core.JdbcTemplate.update*(..))";
	private static final String	COMMON_TEMPLATE_BATCH_EXPRESSION	= "execution(* org.springframework.jdbc.core.JdbcTemplate.batch*(..))";

	private static final String	NAMED_TEMPLATE_EXPRESSION			= "execution(* org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.query*(..))";
	private static final String	NAMED_TEMPLATE_EXECUTE_EXPRESSION	= "execution(* org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.execute*(..))";
	private static final String	NAMED_TEMPLATE_UPDATE_EXPRESSION	= "execution(* org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate.update*(..))";

	public static final String	REPOSITORY_EXPRESSION				= SIMPLE_TEMPLATE_EXPRESSION + " or "
																			+ SIMPLE_TEMPLATE_UPDATE_EXPRESSION + "or"
																			+ SIMPLE_TEMPLATE_BATCH_EXPRESSION + "or"
																			+ COMMON_TEMPLATE_EXPRESSION + " or "
																			+ COMMON_TEMPLATE_EXECUTE_EXPRESSION
																			+ " or "
																			+ COMMON_TEMPLATE_UPDATE_EXPRESSION
																			+ " or " + COMMON_TEMPLATE_BATCH_EXPRESSION
																			+ " or " + NAMED_TEMPLATE_EXPRESSION
																			+ " or "
																			+ NAMED_TEMPLATE_EXECUTE_EXPRESSION
																			+ " or " + NAMED_TEMPLATE_UPDATE_EXPRESSION;
	private static final String	ASPECT_NAME_PROPERTY				= "aspectName";
	private static final String	DECLARATION_ORDER_PROPERTY			= "declarationOrder";
	private static final String	EXPRESSION							= "expression";

	private static final int	METHOD_INDEX						= 0;
	private static final int	POINTCUT_INDEX						= 1;
	private static final int	ASPECT_INSTANCE_FACTORY_INDEX		= 2;

	public BeanDefinition parse(Element element, ParserContext context) {

		registerProxyCreator(context);

		// for add composite bean definition
		CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), context
				.extractSource(element));
		context.pushContainingComponent(compositeDef);

		parseTargetAspectBean(context);

		parsePointcut(element, context);

		List<AbstractBeanDefinition> beanDefinitions = new ArrayList<AbstractBeanDefinition>();
		List<RuntimeBeanReference> beanReferences = new ArrayList<RuntimeBeanReference>();

		// register ref property with target aspect
		beanReferences.add(new RuntimeBeanReference(BEAN_NAME_TARGET_ASPECT));

		parseAdvice(context, beanDefinitions, beanReferences, element);

		AspectComponentDefinition aspectComponentDefinition = createAspectComponentDefinition(element, beanDefinitions,
				beanReferences, context);
		context.pushContainingComponent(aspectComponentDefinition);

		context.popAndRegisterContainingComponent();

		return null;
	}

	private void registerProxyCreator(ParserContext context) {
		RootBeanDefinition creatorDefinition = new RootBeanDefinition(DefaultAdvisorAutoProxyCreator.class);
		context.getReaderContext().registerWithGeneratedName(creatorDefinition);
	}

	private void parsePointcut(Element element, ParserContext context) {
		AbstractBeanDefinition pointcutDefinition = createPointcutDefinition();
		pointcutDefinition.setSource(context.extractSource(element));

		context.getRegistry().registerBeanDefinition(BEAN_NAME_OF_REPOSITORY_POINTCUT, pointcutDefinition);

		context.registerComponent(new PointcutComponentDefinition(BEAN_NAME_OF_REPOSITORY_POINTCUT, pointcutDefinition,
				REPOSITORY_EXPRESSION));

	}

	private AspectComponentDefinition createAspectComponentDefinition(Element element, List beanDefs, List beanRefs,
			ParserContext context) {

		BeanDefinition[] beanDefArray = (BeanDefinition[]) beanDefs.toArray(new BeanDefinition[beanDefs.size()]);
		BeanReference[] beanRefArray = (BeanReference[]) beanRefs.toArray(new BeanReference[beanRefs.size()]);
		Object source = context.extractSource(element);
		return new AspectComponentDefinition(ASPECT_ID, beanDefArray, beanRefArray, source);
	}

	private void parseAdvice(ParserContext context, List beanDefinitions, List beanReferences, Element element) {

		// create the method factory bean
		RootBeanDefinition methodDefinition = new RootBeanDefinition(MethodLocatingFactoryBean.class);
		methodDefinition.getPropertyValues().addPropertyValue("targetBeanName", BEAN_NAME_TARGET_ASPECT);
		methodDefinition.getPropertyValues().addPropertyValue("methodName", TARGET_METHOD_NAME);
		methodDefinition.setSynthetic(true);

		// create instance factory definition
		RootBeanDefinition aspectFactoryDef = new RootBeanDefinition(SimpleBeanFactoryAwareAspectInstanceFactory.class);
		aspectFactoryDef.getPropertyValues().addPropertyValue("aspectBeanName", BEAN_NAME_TARGET_ASPECT);
		aspectFactoryDef.setSynthetic(true);

		// register the pointcut
		AbstractBeanDefinition adviceDef = createAdviceDefinition(context, methodDefinition, aspectFactoryDef,
				beanDefinitions, beanReferences, element);

		// configure the advisor
		RootBeanDefinition advisorDefinition = new RootBeanDefinition(AspectJPointcutAdvisor.class);
		advisorDefinition.setSource(context.extractSource(element));
		advisorDefinition.getConstructorArgumentValues().addGenericArgumentValue(adviceDef);

		// register the final advisor
		context.getReaderContext().registerWithGeneratedName(advisorDefinition);
	}

	private AbstractBeanDefinition createAdviceDefinition(ParserContext context, RootBeanDefinition methodDefinition,
			RootBeanDefinition aspectFactoryDef, List beanDefinitions, List beanReferences, Element element) {

		RootBeanDefinition adviceDefinition = new RootBeanDefinition(AspectJAroundAdvice.class);
		adviceDefinition.setSource(context.extractSource(element));

		adviceDefinition.getPropertyValues().addPropertyValue(ASPECT_NAME_PROPERTY, BEAN_NAME_TARGET_ASPECT);
		adviceDefinition.getPropertyValues().addPropertyValue(DECLARATION_ORDER_PROPERTY, new Integer(0));

		ConstructorArgumentValues cav = adviceDefinition.getConstructorArgumentValues();
		cav.addIndexedArgumentValue(METHOD_INDEX, methodDefinition);

		Object pointcut = parsePointcutProperty(element, context);
		if (pointcut instanceof BeanDefinition) {
			cav.addIndexedArgumentValue(POINTCUT_INDEX, pointcut);
			beanDefinitions.add(pointcut);
		}

		cav.addIndexedArgumentValue(ASPECT_INSTANCE_FACTORY_INDEX, aspectFactoryDef);

		return adviceDefinition;
	}

	private Object parsePointcutProperty(Element element, ParserContext context) {
		AbstractBeanDefinition pointcutDefinition = createPointcutDefinition();
		pointcutDefinition.setSource(context.extractSource(element));
		return pointcutDefinition;
	}

	protected AbstractBeanDefinition createPointcutDefinition() {
		RootBeanDefinition beanDefinition = new RootBeanDefinition(AspectJExpressionPointcut.class);
		beanDefinition.setScope(BeanDefinition.SCOPE_PROTOTYPE);
		beanDefinition.setSynthetic(true);
		beanDefinition.getPropertyValues().addPropertyValue(EXPRESSION, REPOSITORY_EXPRESSION);
		return beanDefinition;
	}

	private AbstractBeanDefinition parseTargetAspectBean(ParserContext context) {
		// build target aspect bean definition
		AbstractBeanDefinition pointCutDefinition = createTargetAspectDefinition();
		pointCutDefinition.setSource(new NullElementSource());

		// register target aspect bean definition
		context.getRegistry().registerBeanDefinition(BEAN_NAME_TARGET_ASPECT, pointCutDefinition);
		return pointCutDefinition;
	}

	protected AbstractBeanDefinition createTargetAspectDefinition() {
		RootBeanDefinition beanDefinition = new RootBeanDefinition(PersistenceContainer.class);
		beanDefinition.setScope(BeanDefinition.SCOPE_SINGLETON);
		beanDefinition.setSynthetic(true);
		beanDefinition.getPropertyValues().addPropertyValue(PROPERTY_REPOSITORY,
				new RuntimeBeanReference(PROPERTY_REPOSITORY));
		return beanDefinition;
	}
}
